import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.containsString;
...
assertThat("Hello world", containsString("world"));Qui suis je ?
N’hésitez pas à interrompre ou à intervenir
Merci de ne pas faire de bruit :)
Comment ? Un questionnaire à choix multiples
Quand ? La dernière heure avec Richard Liot
Tester ce n’est pas que vérifier que son application marche!
C’est savoir rapidement quand l’application ne marche plus
où dans le code
et pourquoi
Construire sa couverture de tests = construire ses TNR (Tests de Non Régression)
Impact sur la conception
Modularité & testabilité
La méthodologie eXtreme Programming est une méthode de gestion de projet qui applique à l’extrême les principes de ceux des méthodes Agiles.
on se concentre sur les besoins du client ;
mise en place d’un développement itératif (sprints courts de 2/3 semaines) et de l’intégration continue.
La méthode XP s’appuie sur :
une forte réactivité au changement des besoins du client ;
un travail d’équipe ;
la qualité du travail fourni ;
la qualité des tests effectués au plus tôt.
Il s’agit d’une technique de conception où le programmeur écrit d’abord le test avant de produire le moindre code.
Le développeur écrit ensuite le code pour que le test passe.
Une fois son test finalisé, il pourra être libre de refactorer autant qu’il le souhaite jusqu’à obtenir un code « propre ».
C’est une idée simple mais complexe à mettre en oeuvre.
Courbe d’apprentissage plus lente de prime abord.
Vérifier la bonne compréhension des fonctionnalités
Meilleure couverture de tests automatisés
Facilité d’écriture des tests avant le code « métier »
Ils servent à promouvoir et vérifier la qualité et la fiabilité du code
Enfin, jusqu’Ã une certaine limite !!
Ecriture d’un test pour une fonctionnalité
Le test est « failed »
Codage de la fonctionnalité minimale
Vérification du cas passant
Répéter l’opération en enrichissant la fonctionnalité en refactorisant
Red / Green / Refactor
Les tests unitaires consistent à tester individuellement les composants d’une application.
On pourra ainsi valider la qualité du code et les performances d’un module.
Ces tests sont exécutés pour valider l’intégration des différents modules entre eux et dans leur environnement d’exploitation définitif.
Ils permettront de mettre en évidence des problèmes d’interfaces entre différents programmes.
Ces tests ont pour but de vérifier la conformité de l’application développée avec le cahier des charges initial.
Ils sont donc basés sur les spécifications fonctionnelles et techniques.
L’écriture de tests fonctionnels automatisés représente un effort important.
Que pouvez-vous accepter pour valider une fonctionnalité ?
Conformité des fonctionnalités demandées.
Les temps de réponses sont-ils corrects (chargement d’une page HTML, réponse d’une API, …) ?
Ce sont des tests permettant de mesurer les temps de réponses du système en fonction des sollicitations.
Les tests de charge simulent un nombre prédéfini d’utilisateurs en simultané pour mesurer le dimensionnement de l’infrastructure nécessaire (serveurs, bande passante sur le réseau, …)
Les tests de performance permettent de récupérer des métriques (temps de réponses, percentile)
Les tests en « boite noire » consistent à examiner uniquement les fonctionnalités d’une application.
Les tests en « boîte blanche » consistent à examiner le fonctionnement d’une application et sa structure interne, ses processus, plutôt que ses fonctionnalités.
Les tests en « boîte grise » compilent ces deux précédentes approches : ils éprouvent à la fois les fonctionnalités et le fonctionnement d’un système.
En méthode « boîte noire », on vérifie que la voiture fonctionne en allumant les lumières, en klaxonnant et en tournant la clé pour que le moteur s’allume. Si tout se passe comme prévu, la voiture fonctionne.
En méthode « boîte blanche », on emmène la voiture chez le garagiste, qui regarde le moteur ainsi que toutes les autres parties (mécaniques comme électriques) de la voiture. Si elle est en bon état, elle fonctionne.
En méthode « boîte grise », on emmène la voiture chez le garagiste, et en tournant la clé dans la serrure, on vérifie que le moteur s’allume, et le garagiste observe en même temps le moteur pour s’assurer qu’il démarre bien selon le bon processus.
JUnit, TestNG (java)
Jasmine, Karma, Mocha (javascript)
nUnit (.Net)
SimpleTest (PHP)
dUnit (Delphi)
cppUnit (C++) etc..
Mockito, EasyMock, PowerMock (java)
Sinon JS, Jest, Rhino Mocks (javascript)
TypeMock, Moq (.Net)
JMeter, Gatling, Taurus, Locust
Cobertura, JaCoCo (java)
Coverage.py (python)
Bullseye Coverage (C++, C)
NCover, dotCover (.Net)
Tests manuels (SoapUI, Postman)
Tests d’API (frameworks REST Assured, Karate)
Outils de tests de sécurité
type SAST (Source Code Analysis Tools)
type DAST (Dynamic Application Security Testing)
Tests IHM (GUI testing) comme Seleniumn, QTP ou Cucumber
…
PIC : Plateforme agrégeant des outils permettant l’IC
Test immédiat des modifications
Notification rapide en cas de problèmes
Les problèmes d’intégration sont détectés et réparés de façon continue
réticences à la mise en oeuvre
difficultés de rédaction et de codage
couverture du code testé
temps nécessaire à la rédaction des cas de tests
véracité des cas de tests
temps nécessaire à la maintenance des cas de tests
les cas de tests doivent être répétables
complexité ⇒ base de données, fichiers
…
| JUnit 4 | La description |
|---|---|
@BeforeClass | La méthode est exécutée une fois avant le début de tous les tests. |
@AfterClass | La méthode est exécutée une fois tous les tests joués. |
@Ignore or @Ignore("Why disabled") | Marque que le test doit être désactivé. |
@Test (expected = Exception.class) | Échec si la méthode ne lance pas l’exception nommée. |
@Test(timeout=100) | Échoue si la méthode prend plus de 100 millisecondes. |
| JUnit 4 | La description |
|---|---|
import org.junit.* | Package pour l’utilisation des annotations. |
@Test | Identifie une méthode en tant que méthode de test. |
@Before | Exécuté avant chaque test (identifié par une méthode). |
@After | Exécuté après chaque test (chaque méthode). |
| Déclaration | La description |
|---|---|
fail([message]) | Laissez la méthode échouer. |
assertTrue([message,] boolean condition) | Vérifie que la condition booléenne est vraie. |
assertFalse([message,] boolean condition) | Vérifie que la condition booléenne est fausse. |
assertEquals([message,] expected, actual) | Teste que deux valeurs sont identiques. |
assertEquals([message,] expected, actual, tolerance) | Vérifiez que les valeurs float ou double correspondent. |
Besoin de plus de puissance ? utiliser AssertJ ou Hamcrest.
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.containsString;
...
assertThat("Hello world", containsString("world"));| Déclaration | La description |
|---|---|
assertNull([message,] object) | Vérifie que l’objet est nul. |
assertNotNull([message,] object) | Vérifie que l’objet n’est pas nul. |
assertSame([message,] expected, actual) | Vérifie que les deux variables se réfèrent au même objet. |
assertNotSame([message,] expected, actual) | Vérifie que les deux variables se réfèrent à des objets différents. |
une API pour écrire des tests
un mécanisme pour découvrir et lancer les tests
une API pour lancer les tests (pour les outils comme Eclipse)
| Ce qui change (résumé) | JUnit 4 | JUnit 5 |
|---|---|---|
package | org.junit | org.junit.jupiter.api |
plus de classes/méthodes publiques | public class MyTest | class MyTest |
quelques annotations | @Ignore, @Category | @Disable, @Tag |
les assertions | org.junit.Assert | org.junit.jupiter.api.Assertions |
ordre des paramètres | assertEquals("Error message", expected, actual) | assertEquals(expected, actual, "Error message") |
suppression de assertThat | org.junit.Assert.assertThat | Utiliser directement la librairie Hamcrest |
Comment donner du sens à vos tests unitaires ?
En appliquant certains principes du Behavior Driven Development (BDD)
Pourquoi ?
Afin d’obtenir une classe de tests unitaires claire et maintenable.
Les tests doivent être
compréhensibles, lisibles et facilement modifiables
automatisables, répétables et exécutés rapidement
Mockito est un framework Java, permettant :
de mocker ou espionner des objets,
simuler et vérifier des comportements,
ou encore simplifier l’écriture de tests unitaires.
Exemple dans une Architecture Hexagonale sur les principes du Domain Driven Development (DDD) :
L’approche DDD vise, à isoler un domaine métier avec les caractéristiques suivantes:
Approfondissement des règles métier spécifiques en accord avec le modèle d’entreprise, la stratégie et les processus métier.
Isolation des autres domaines métier et des autres couches de l’architecture de l’application.
Modèle construit avec un couplage faible avec les autres couches de l’application.
Facilement maintenable, testable et versionnable.
Modèle conçu avec le moins de dépendances possibles avec une technologie ou un framework.
« Permettre à une application d’être pilotée aussi bien par des utilisateurs que par des programmes, des tests automatisés ou des scripts batchs, et d’être développée et testée en isolation de ses éventuels systèmes d’exécution et bases de données. »
Alistair Cockburn en 2005 (hexagonal architecture).
L’architecture hexagonale repose sur trois principes et techniques:
Séparer explicitement la logique métier de la partie exposition (client-side) et persistence (server-side).
Les dépendances partent des couches techniques (client-side / server-side) vers la couche logique métier
Il faut isoler les couches en utilisant des ports et des adaptateurs
En effet, il sera très intuitif d’écrire son test en suivant la notion //Given //When //Then, et nous verrons que Mockito met l’accent sur la 1ère et la 3ème notion.
Deux possibilités :
Ajouter l’annotation @RunWith comme suit :
@RunWith(MockitoJunitRunner.class)
public class MyTestClass {
}Ou à l’initialisation dans la méthode d’initialisation (ici setUp())
private AutoCloseable closeable;
...
@Before
public void setUp() {
closeable = MockitoAnnotations.openMocks(this);
}
@After
public void tearDown() throws Exception {
closeable.close();
}Il est conseillé de libérer la ressource après chaque test (voir méthode tearDown()).
Mockito est capable de « stubber » (bouchonner) des classes concrètes mais aussi des interfaces.
On peut appeler la méthode mock(…) sur une classe :
User user = Mockito.mock(User.class);
Ou placer une annotation si la variable est en instance de classe
@Mock User user;
Retour d’une valeur unique
Mockito.when(user.getLogin()).thenReturn(‘user1’);
Faire appel à la méthode d’origine
Mockito.when(user.getLogin()).thenCallRealMethod();
Levée d’exceptions
Mockito.when(user.getLogin()).thenThrow(new RuntimeException());
@SpyLa différence entre @Mock et @Spy réside dans le fait que la deuxième permet d’instancier l’objet mocké; on peut ainsi effectuer un mock partiel.
Quand on appelle une méthode de l’objet « espionné »
la vraie méthode est appelée,
à moins qu’un comportement ai été défini.
@Spy User user = new User(‘user1’); user.getLogin() // retourne user1 Mockito.when(user.getPassword()).thenReturn(‘top secret’);
verify(user).getLogin(); // le test passe si getLogin() est appelée avant la fin du timeout (ici 100 ms) verify(user, timeout(100)).getLogin(); // le test passe si il n'existe aucune autre interaction sur le mock (non vérifiée) verifyNoMoreInteractions(luser);
Mockito permet également d’injecter des ressources (classes nécessaires au fonctionnement de l’objet mocké), en utilisant l’annotation @InjectMock.
L’injection des mocks dans l’objet marqué par @InjectMock se fera (par ordre de priorité) :
injection par le constructeur
injection par la méthode de type « setter »
injection par l’attribut (même si celui-ci est private)
ouvrir le pdf tp/tp-mocks/tp-mocks.pdf
C’est à vous ;)